/* ----------------------------------------------------------------------
 *	Shadow Quest
 *	Copyright (C) 2009 Prageeth Silva <prageeth@thenewcoders.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *  ----------------------------------------------------------------------
 */
package sq.game;

import java.util.ArrayList;
import java.util.Arrays;

import map.Item;
import map.World;
import org.newdawn.slick.Animation;

import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import sq.ImageLoader;

import sq.LoadScript;
import sq.game.Team.Diplomacy;
import units.Fighter;
import units.Player;
import units.Unit;
import units.villagers.Villager;

public final class Quest {

    private static final int ANIMATION_INTERVAL = 5000;
    private static final String FILENAME = "script.lua";
    private static final String MAPNAME = "map.tmx";
    private static final String FUNC_LOAD = "load";

    private ArrayList<Team> teams;
    private Player currPLayer;
    private String playerName;
    // The duration of a status message to be shown
    private int[] statusDur;
    private ArrayList<String> status;
    private boolean gameStarted, gameEnded;

    private boolean display;
    private Animation epilogue, prologue;
    private int nEpi, nPro;

    private World world;
    private LoadScript script;
    private GameCursor cursor;
    private String title, sPath;

    private Diplomacy[][] dip;

    public Quest(String title, String dirPath)
            throws SlickException {

        this.initialiseDiplomacy();
        this.setPlayerName("Player");
        this.status = new ArrayList<String>();

        this.teams = new ArrayList<Team>(Team.MAX_TEAMS);
        for (int i = 0; i < Team.MAX_TEAMS; i++) {
            this.getTeams().add(new Team(i));
        }

        this.setDirectoryPath(dirPath);
        this.setTitle(title);

    }

    public void initialiseDiplomacy() {
        this.dip = new Diplomacy[Team.MAX_TEAMS][Team.MAX_TEAMS];
        for (int i = 0; i < Team.MAX_TEAMS; i++) {
            Arrays.fill(this.dip[i], Diplomacy.NEUTRAL);
        }
    }

    public void initialiseQuest()
            throws SlickException {

        this.setScript(new LoadScript(this.getDirectoryPath() + Quest.FILENAME));
        this.loadGame();
        this.setCursor(new GameCursor(this));
        this.setEpilogue(ImageLoader.getEpilogueFrames());
        this.setPrologue(ImageLoader.getPrologueFrames());

    }

    private void loadGame()
            throws SlickException {
        this.setWorld(new World(this));
        this.getScript().runScriptFunction(Quest.FUNC_LOAD, this);
    }

    public void update(Input input, int delta)
            throws SlickException {
        this.getWorld().update(input, delta);
        this.getCursor().update(input, delta);
    }

    /** Render the entire screen, so it reflects the current game state.
     * @param gc The Slick game container object.
     * @param g The Slick graphics object, used for drawing.
     */
    public void render(GameContainer gc, Graphics g)
            throws SlickException {
        // render the world
        this.getWorld().render(g);

        // render the panel
        this.getCurrentPlayer().getPanel().render(g, this.getWorld().getWeatherGen());

        // render the status on top 
        this.getCurrentPlayer().renderStatus(g);

        // render the mouse cursor
        this.getCursor().render(gc, g);
    }

    /** Checks if the game has already started.
     * @return True only if the game has started.
     */
    public boolean isGameStarted() {
        return this.gameStarted;
    }

    /** Checks if the game has already started.
     * @return True only if the game has started.
     */
    public boolean isGameEnded() {
        return this.gameEnded;
    }

    /** Starts the game.
     */
    public void startGame() {
        this.gameStarted = true;
    }

    /** Ends the game.
     */
    public void endGame() {
        this.gameEnded = true;
    }

    /** Reset the game.
     */
    public void resetGame() {
        this.gameStarted = false;
        this.gameEnded = false;
    }

    public World getWorld() {
        return this.world;
    }

    private void setWorld(World world) {
        this.world = world;
    }

    private LoadScript getScript() {
        return this.script;
    }

    private void setScript(LoadScript script) {
        this.script = script;
    }

    public String getDirectoryPath() {
        return this.sPath;
    }

    public final void setDirectoryPath(String path) {
        this.sPath = path;
    }

    /**
     * @return the title
     */
    public String getTitle() {
        return this.title;
    }

    /**
     * @param title the title to set
     */
    private void setTitle(String title) {
        this.title = title;
    }
    
    private GameCursor getCursor() {
        return this.cursor;
    }

    private void setCursor(GameCursor cursor) {
        this.cursor = cursor;
    }

    public void loadWorld()
            throws SlickException {
        this.loadWorld("map/", Quest.MAPNAME);
    }

    public void loadWorld(String path, String mapName)
            throws SlickException {
        path = path.endsWith("/") ? path : (path + "/");
        String dirPath = this.getDirectoryPath() + path;
        this.getWorld().loadMap(dirPath + mapName, dirPath);
    }

    public Villager createVillager(String unitName, String displayName, float x, float y) {
        // TODO
        Villager v = null;
        //this.getWorld().
        //v.mo
        this.getTeams().get(Team.VILLAGER_TEAM).getTeamMembers().add(v);
        return v;
    }

    public Villager createVillager(String unitName, String displayName, int x, int y, boolean centerTile) {
        Villager v = this.createVillager(unitName, displayName, 0, 0);
        v.moveToTile(x, y, centerTile);
        return v;
    }

    public Player createPlayer(int team, String unitName, String displayName, float x, float y)
            throws SlickException {
        Player p = new Player(this.getWorld(), team, unitName);
        p.moveTo(x, y);
        p.setName(displayName);
        this.getWorld().setPlayer(p);
        this.getTeams().get(team).getTeamMembers().add(p);
        this.setCurrentPlayer(p);
        this.setPlayerName(displayName);
        return p;
    }

    public Player createPlayer(int team, String unitName, String displayName, int x, int y, boolean centerTile)
            throws SlickException {
        Player p = this.createPlayer(team, unitName, displayName, 0, 0);
        p.moveToTile(x, y, centerTile);
        return p;

    }

    public Fighter createFighter(int team, String unitName, String displayName, float x, float y)
            throws SlickException {
        Fighter f = new Fighter(this.getWorld(), team, unitName);
        f.moveTo(x, y);
        f.setName(displayName);
        this.getWorld().addOtherUnit(f);
        this.getTeams().get(team).getTeamMembers().add(f);
        return f;
    }

    public Fighter createFighter(int team, String unitName, String displayName, int x, int y, boolean centerTile)
            throws SlickException {
        Fighter f = this.createFighter(team, unitName, displayName, 0, 0);
        f.moveToTile(x, y, centerTile);
        return f;
    }

    public Item createItem(String itemName, String displayName, int value, float x, float y)
            throws SlickException {
        Item i = new Item(this.getWorld(), itemName, displayName, value);
        i.moveTo(x, y);
        this.getWorld().addItem(i);
        return i;
    }

    public Item createItem(String itemName, String displayName, int value, int x, int y, boolean centerTile)
            throws SlickException {
        Item i = this.createItem(itemName, displayName, value, 0, 0);
        i.moveToTile(x, y, centerTile);
        return i;
    }

    public Item createItem(String itemName, String displayName, float x, float y)
            throws SlickException {
        return this.createItem(itemName, displayName, 1, x, y);
    }

    public Item createItem(String itemName, String displayName, int x, int y, boolean centerTile)
            throws SlickException {
        return this.createItem(itemName, displayName, 1, x, y, centerTile);
    }

    public Item createItem(String itemName, String displayName, int value, Unit unit)
            throws SlickException {
        Item i = new Item(this.getWorld(), itemName, displayName, value);
        this.getWorld().addItem(i);
        unit.pickUpItem(i);
        return i;
    }

    public Item createItem(String itemName, String displayName, Unit unit)
            throws SlickException {
        Item i = new Item(this.getWorld(), itemName, displayName, 1);
        this.getWorld().addItem(i);
        unit.pickUpItem(i);
        return i;
    }

    public void makeRival(int team1, int team2) {
        this.setDiplomacy(team1, team2, Diplomacy.ENEMY);
    }

    public void makeAlly(int team1, int team2) {
        this.setDiplomacy(team1, team2, Diplomacy.ALLY);
    }

    public void makeNeutral(int team1, int team2) {
        this.setDiplomacy(team1, team2, Diplomacy.NEUTRAL);
    }

    public void resetGamePlay() {
        this.initialiseDiplomacy();
        for (Team t : this.getTeams()) {
            t.resetTeamMembers();
        }
        this.getWorld().clearObjects();
    }

    public ArrayList<Team> getTeams() {
        return this.teams;
    }

    public ArrayList<String> getStatus() {
        return this.status;
    }

    public void setStatus(ArrayList<String> status) {
        this.status = status;
    }

    public void addMsgToStatus(String text) {
        this.addMsgToStatus(text, false);
    }

    public void addMsgToStatus(String text, boolean repeat) {
        if (isGameStarted()) {
            boolean ok = true;
            if (this.getStatus().size() > 0 && !repeat) {
                String s = this.getStatus().get(this.getStatus().size() - 1);
                ok = !s.equals(text);
            }
            if (ok) {
                this.getStatus().add(text);
                int end = (this.getStatus().size() > Player.STATUS_MAX) ? Player.STATUS_MAX - 1 : this.getStatus().size() - 1;
                for (int i = 1; i < Player.STATUS_MAX; i++) {
                    this.getStatusDur()[i - 1] = this.getStatusDur()[i];
                }
                this.getStatusDur()[end] = Player.STATUS_DURATION;
            }
        }
    }

    public int[] getStatusDur() {
        return this.statusDur;
    }

    public void setStatusDur(int[] statusDur) {
        this.statusDur = statusDur;
    }

    public Player getCurrentPlayer() {
        return this.currPLayer;
    }

    private void setCurrentPlayer(Player player) {
        this.currPLayer = player;
    }

    public String getPlayerName() {
        return this.playerName;
    }

    public void setCurrentPlayerName(String name) {
        this.setPlayerName(name);
    }

    public void setPlayerName(String name) {
        this.playerName = name;
    }

    /**
     * @return the epilogue
     */
    public Animation getEpilogue() {
        return this.epilogue;
    }

    /**
     * @param epilogue the epilogue to set
     */
    private void setEpilogue(Image image[]) {
        this.epilogue = new Animation(image, Quest.ANIMATION_INTERVAL);
        this.epilogue.setLooping(false);
    }

    /**
     * @return the prologue
     */
    public Animation getPrologue() {
        return this.prologue;
    }

    /**
     * @param prologue the prologue to set
     */
    private void setPrologue(Image image[]) {
        this.prologue = new Animation(image, Quest.ANIMATION_INTERVAL);
        this.prologue.setLooping(false);
    }

    /**
     * @return the display
     */
    public boolean isDisplayImagesLoaded() {
        return display;
    }

    /**
     * @param display the display to set
     */
    private void setDisplayImagesLoaded(boolean display) {
        this.display = display;
    }

    public void loadDisplayImages()
            throws SlickException {
        boolean b = ImageLoader.loadEpilogueImages(this.getDirectoryPath());
        b &= ImageLoader.loadPrologueImages(this.getDirectoryPath());
        this.setDisplayImagesLoaded(b);
    }

    public void winQuest()
    {
        this.endGame();
    }

    public void setDiplomacy(int team1, int team2, Diplomacy dip) {
        if ((team1 == team2) || (dip == Diplomacy.SAME)) {
            return;
        }
        this.dip[team1][team2] = dip;
        this.dip[team2][team1] = dip;
    }

    public void setDiplomacy(Team team1, Team team2, Diplomacy dip) {
        this.setDiplomacy(team1.getTeamNumber(), team2.getTeamNumber(), dip);
    }

    public Diplomacy getDiplomacy(int team1, int team2) {
        if (team1 == team2) {
            return Diplomacy.SAME;
        }
        return this.dip[team1][team2];
    }

    public Diplomacy getDiplomacy(Unit unit1, Unit unit2) {
        return this.getDiplomacy(unit1.getTeam(), unit2.getTeam());
    }

    public Diplomacy getDiplomacy(Team team1, Team team2) {
        return this.getDiplomacy(team1.getTeamNumber(), team2.getTeamNumber());
    }

    public void makeTeamLeader(int team, Unit unit) {
        this.getTeams().get(team).setTeamLeader(unit);
    }

    public void makeTeamLeader(Team team, Unit unit) {
        this.makeTeamLeader(team.getTeamNumber(), unit);
    }

}
